use std::collections::HashSet;

pub type Letter = u8;

pub const MAX_TRY: usize = 6;
pub const WORD_LENGTH: usize = 5;
pub const ALPHABET_SIZE: usize = 26;
pub const ALPHABET_START: Letter = 'A' as Letter;
pub const ALPHABET_INPUT_START: Letter = 'a' as Letter;

pub type WordData = [Letter; WORD_LENGTH];
pub type WordResult = [LetterResult; WORD_LENGTH];
pub type AlphabetResult = [LetterResult; ALPHABET_SIZE];

// build valid set from list
pub fn build_set_from_list<'a>(list: &Vec<&'a str>) -> HashSet<&'a str> {
    let mut set: HashSet<&str> = HashSet::new();
    for s in list {
        if !set.insert(s) {
            panic!("ERROR: DUPLICATE WORDS IN LIST");
        }
    }
    set
}

// judge validity
pub fn is_valid(word: &String, valid_set: &HashSet<&str>) -> bool {
    // judge validity by word length
    if word.len() != WORD_LENGTH {
        return false;
    }
    // judge validity by dictionary
    valid_set.contains(word as &str)
}

// update difficult mode requirements
pub fn update_difficult_mode(
    guess: &String,
    word_results: &WordResult,
    correct: &mut Vec<(char, usize)>,
    misplaced: &mut Vec<char>,
) {
    *correct = Vec::new();
    *misplaced = Vec::new();
    let chars: Vec<char> = guess.chars().collect();
    for i in 0..WORD_LENGTH {
        match word_results[i] {
            LetterResult::Correct => {
                correct.push((chars[i], i));
            }
            LetterResult::Misplaced => {
                misplaced.push(chars[i]);
            }
            _ => {}
        }
    }
}

// check difficult mode requirements
pub fn check_difficult_mode(
    word: &String,
    correct: &Vec<(char, usize)>,
    misplaced: &Vec<char>,
) -> bool {
    let chars: Vec<char> = word.chars().collect();
    let mut used: [bool; WORD_LENGTH] = [false; WORD_LENGTH];
    for (ch, id) in correct {
        if chars[*id] == *ch {
            used[*id] = true;
        } else {
            return false;
        }
    }
    for ch in misplaced {
        let mut missing: bool = true;
        for i in 0..WORD_LENGTH {
            if chars[i] == *ch && !used[i] {
                used[i] = true;
                missing = false;
                break;
            }
        }
        if missing {
            return false;
        }
    }
    true
}

// get letters
pub fn get_letters(word: &String) -> WordData {
    let mut data: WordData = [0; WORD_LENGTH];
    for (id, chr) in word.char_indices() {
        data[id] = (chr as Letter) - ALPHABET_START
    }
    data
}

// result of a single letter
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum LetterResult {
    Unknown,
    TooMany,
    Misplaced,
    Correct,
}

// calculate word result
pub fn calculate_word_result(ans_data: &WordData, guess_data: &WordData) -> WordResult {
    let mut guess_results: WordResult = [LetterResult::Unknown; WORD_LENGTH];
    let mut ans_results: WordResult = [LetterResult::Unknown; WORD_LENGTH];

    for i in 0..WORD_LENGTH {
        if ans_data[i] == guess_data[i] {
            guess_results[i] = LetterResult::Correct;
            ans_results[i] = LetterResult::Correct;
        }
    }

    for i in 0..WORD_LENGTH {
        if guess_results[i] == LetterResult::Unknown {
            let c: Letter = guess_data[i];
            for j in 0..WORD_LENGTH {
                if ans_data[j] == c && ans_results[j] == LetterResult::Unknown {
                    guess_results[i] = LetterResult::Misplaced;
                    ans_results[j] = LetterResult::Misplaced;
                    break;
                }
            }
        }
    }

    for i in 0..WORD_LENGTH {
        if guess_results[i] == LetterResult::Unknown {
            guess_results[i] = LetterResult::TooMany
        }
    }

    guess_results
}

// update alphabet result
pub fn update_alphabet_result(
    alphabet_results: &mut AlphabetResult,
    guess_data: &WordData,
    guess_results: &WordResult,
) {
    for i in 0..WORD_LENGTH {
        let alphabet_result = &mut alphabet_results[guess_data[i] as usize];
        let guess_result = &guess_results[i];
        if *alphabet_result < *guess_result {
            *alphabet_result = *guess_result
        }
    }
}

// calculate word result and update alphabet result
pub fn calculate_result(
    alphabet_results: &mut AlphabetResult,
    ans_data: &WordData,
    guess_data: &WordData,
) -> WordResult {
    let guess_results: WordResult = calculate_word_result(ans_data, guess_data);
    update_alphabet_result(alphabet_results, guess_data, &guess_results);
    guess_results
}
